using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Oops.Xna.Framework.Audio;
using Oops.Xna.Framework.Diagnostics;
using Oops.Xna.Framework.GameState;
using Oops.Xna.Framework.Input;
using Oops.Xna.Framework.UI;
using Oops.Xna.Framework.Viewpoint;
using Oops.Xna.Framework.Physics;
using Oops.Xna.Framework.Graphics;

namespace Oops.Xna.Framework.Testing.App
{
	public class TestGame : Game
	{
		private GraphicsDeviceManager graphics;
		private ScreenSpaceService screenSpace;

		private AudioComponent audio;
		private InputComponent input;
		private GameStateComponent gameState;

		private ScreenshotComponent screenshot;
		private FrameRateComponent frameRate;
		private GCComponent gc;

		protected BasicEffect basicEffect;
		private PssmEffect pssmEffect;

		/// <summary>
		/// Initializes a new instance of the <see cref="TestGame"/> class.
		/// </summary>
		public TestGame()
		{
			// Set the title.
			this.Window.Title = "Oops!";

			// Create the graphics device manager and subscribe to some of it's events.
			this.graphics = new GraphicsDeviceManager(this);
#if XBOX
			this.graphics.PreferMultiSampling = false; // Shadows are affected by this and predicated tiling I assume.
			this.graphics.PreferredBackBufferWidth = 1280;
			this.graphics.PreferredBackBufferHeight = 720;
			this.graphics.IsFullScreen = true;
#else
			this.graphics.PreferMultiSampling = true;
			this.graphics.PreferredBackBufferWidth = 1024;
			this.graphics.PreferredBackBufferHeight = 576;
#endif
			this.graphics.SynchronizeWithVerticalRetrace = false;
			this.graphics.PreparingDeviceSettings += delegate(object sender, PreparingDeviceSettingsEventArgs e)
			{
#if XBOX
				// Set some XBox 360-specific options on the graphics device.
				//e.GraphicsDeviceInformation.PresentationParameters.MultiSampleQuality = 1; // Shadows are affected by this and predicated tiling I assume.
				//e.GraphicsDeviceInformation.PresentationParameters.MultiSampleType = MultiSampleType.FourSamples; // Shadows are affected by this and predicated tiling I assume.
				e.GraphicsDeviceInformation.PresentationParameters.BackBufferWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
				e.GraphicsDeviceInformation.PresentationParameters.BackBufferHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
				e.GraphicsDeviceInformation.PresentationParameters.AutoDepthStencilFormat = DepthFormat.Depth24Stencil8Single;
				e.GraphicsDeviceInformation.PresentationParameters.EnableAutoDepthStencil = true;
#else
				e.GraphicsDeviceInformation.PresentationParameters.MultiSampleType = MultiSampleType.FourSamples;
#endif
			};
			
			// Create the global game components.  Some components are also services.
			this.input = new InputComponent(this);
			this.audio = new AudioComponent(this);
			this.gameState = new GameStateComponent(this);
			this.frameRate = new FrameRateComponent(this);
			this.gc = new GCComponent(this);
#if !XBOX
			this.screenshot = new ScreenshotComponent(this);
#endif
			// Add the game components to the game's component collection.
			this.Components.Add(this.input);
			this.Components.Add(this.audio);
			this.Components.Add(this.gameState);
			this.Components.Add(this.frameRate);
			this.Components.Add(this.gc);
#if !XBOX
			this.Components.Add(this.screenshot);
#endif
			// Make the game not fixed time step.  The physics component accommodates for variations 
			// in time step.
			this.IsFixedTimeStep = false;
			this.TargetElapsedTime = TimeSpan.FromSeconds(1.0f / 60.0f);
		}

		protected override void Initialize()
		{
			// Initialize the audio component.
			this.audio.SettingsFile = @".\Content\Audio\Audio.xgs";

			// Initialize the game state component.  The first screen added will be the starting screen or 
			// can be explicitly set by setting the current screen property.
			this.gameState.Screens.Add(new MainMenuScreen(this));
			this.gameState.Screens.Add(new Example1Screen(this));
			this.gameState.Screens.Add(new Example2Screen(this));
			this.gameState.Screens.Add(new Example3Screen(this));
			this.gameState.Screens.Add(new Example4Screen(this));
			this.gameState.Screens.Add(new Example5Screen(this));
			this.gameState.Screens.Add(new Example6Screen(this));
			this.gameState.Screens.Add(new Example7Screen(this));
			this.gameState.Screens.Add(new Example8Screen(this));
			this.gameState.Screens.Add(new Example9Screen(this));
			this.gameState.Screens.Add(new PromptScreen(this));

			Point location = this.GraphicsDevice.Viewport.TitleSafeArea.Location;

			// Initialize the frame rate component.
			this.frameRate.Position = new Vector2(location.X + 5, location.Y + 5);
			this.frameRate.FontAssetName = @".\Content\Fonts\default";

			// Initialize the GC component.
			this.gc.Position = new Vector2(location.X + 5, location.Y + 25);
			this.gc.FontAssetName = @".\Content\Fonts\default";

			// Create the screen space service.
			this.screenSpace = new ScreenSpaceService(this);

			// Create and initialize a basic effect for drawing.
			this.basicEffect = new BasicEffect(this.GraphicsDevice, null);
			this.basicEffect.EnableDefaultLighting();
			this.basicEffect.TextureEnabled = true;
			this.basicEffect.PreferPerPixelLighting = true;

			// Create the pssm effect.
			this.pssmEffect = new PssmEffect(this.GraphicsDevice, this.GetContent().Load<Effect>(@".\Content\Effects\PSSM"));
			this.pssmEffect.EnableDefaultLighting();
			this.pssmEffect.DiffuseTextureEnabled = true;
			//this.pssmEffect.PreferPerPixelLighting = true;

			// Add the two effects as services.
			this.AddService<BasicEffect>(this.basicEffect);
			this.AddService<PssmEffect>(this.pssmEffect);

			// Call inherited method.
			base.Initialize();
		}

		protected override void UnloadContent()
		{
			// Dispose of the pssm effect.
			if (this.pssmEffect != null)
			{
				this.pssmEffect.Dispose();
			}

			// Dispose of the basic effect and vertex declaration.
			if (this.basicEffect != null)
			{
				this.basicEffect.Dispose();
			}

			// Remove the two effects as services.
			this.RemoveService<BasicEffect>();
			this.RemoveService<PssmEffect>();

			base.UnloadContent();
		}

		protected override void Update(GameTime gameTime)
		{
			// Switch cameras.
			if (this.input.IsKeyPressed(Keys.Tab) || this.input.IsGamePadButtonPressed(0, Buttons.RightShoulder))
			{
				IViewpointService viewpointService = this.GetService<IViewpointService>(false);
				if (viewpointService != null)
				{
					int index = viewpointService.Cameras.IndexOf(viewpointService.ActiveCamera);
					index = (index + 1) % viewpointService.Cameras.Count;
					viewpointService.Cameras[index].Enabled = true;
				}
			}

			// Toggle wireframe.
			if (this.input.IsKeyPressed(Keys.F1) || this.input.IsGamePadButtonPressed(0, Buttons.DPadLeft))
			{
				if (this.GraphicsDevice.RenderState.FillMode == FillMode.WireFrame)
				{
					this.GraphicsDevice.RenderState.FillMode = FillMode.Solid;
				}
				else
				{
					this.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame;
				}
			}
#if DEBUG
			// Toggle physics aabb drawing.
			if (this.input.IsKeyPressed(Keys.F2) || this.input.IsGamePadButtonPressed(0, Buttons.DPadRight))
			{
				PhysicsComponent physicsService = this.GetService<IPhysicsService>(false) as PhysicsComponent;
				if (physicsService != null)
				{
					physicsService.DrawAABBs = !physicsService.DrawAABBs;
				}
			}
#endif
			// Toggle drawing.
			if (this.input.IsKeyPressed(Keys.F3) || this.input.IsGamePadButtonPressed(0, Buttons.LeftShoulder))
			{
				this.gameState.CurrentScreen.Visible = !this.gameState.CurrentScreen.Visible;
			}

			// Call inherited method.
			base.Update(gameTime);
		}

		protected override void Draw(GameTime gameTime)
		{
			if (!this.gameState.CurrentScreen.Visible)
			{
				this.GraphicsDevice.Clear(ClearOptions.DepthBuffer | ClearOptions.Target, Color.Black, 1.0f, 0);
			}

			// Call inherited method.
			base.Draw(gameTime);
		}
	}
}
